<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN" lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>

<title>Rails 国际化 API — Ruby on Rails Guides</title>
<link rel="stylesheet" type="text/css" href="stylesheets/style.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/print.css" media="print" />

<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shCore.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shThemeRailsGuides.css" />

<link rel="stylesheet" type="text/css" href="stylesheets/fixes.css" />

<link href="images/favicon.ico" rel="shortcut icon" type="image/x-icon" />
</head>
<body class="guide">
  <div id="topNav">
    <div class="wrapper">
      <strong class="more-info-label">更多内容 <a href="http://rubyonrails.org/">rubyonrails.org:</a> </strong>
      <span class="red-button more-info-button">
        更多内容
      </span>
      <ul class="more-info-links s-hidden">
        <li class="more-info"><a href="http://weblog.rubyonrails.org/">博客</a></li>
        <li class="more-info"><a href="http://guides.rubyonrails.org/">指南</a></li>
        <li class="more-info"><a href="http://api.rubyonrails.org/">API</a></li>
        <li class="more-info"><a href="http://stackoverflow.com/questions/tagged/ruby-on-rails">提问</a></li>
        <li class="more-info"><a href="https://github.com/rails/rails">到 GitHub 贡献</a></li>
        <li class="more-info"><a href="https://ruby-china.org/">Ruby China 社区</a></li>
      </ul>
    </div>
  </div>
  <div id="header">
    <div class="wrapper clearfix">
      <h1><a href="index.html" title="返回首页">Rails 指南</a></h1>
      <ul class="nav">
        <li><a class="nav-item" href="index.html">首页</a></li>
        <li class="guides-index guides-index-large">
          <a href="index.html" id="guidesMenu" class="guides-index-item nav-item">指南索引</a>
          <div id="guides" class="clearfix" style="display: none;">
            <hr />
              <dl class="L">
                <dt>新手入门</dt>
                <dd><a href="getting_started.html">Rails 入门</a></dd>
                <dt>模型</dt>
                <dd><a href="active_record_basics.html">Active Record 基础</a></dd>
                <dd><a href="active_record_migrations.html">Active Record 迁移</a></dd>
                <dd><a href="active_record_validations.html">Active Record 数据验证</a></dd>
                <dd><a href="active_record_callbacks.html">Active Record 回调</a></dd>
                <dd><a href="association_basics.html">Active Record 关联</a></dd>
                <dd><a href="active_record_querying.html">Active Record 查询接口</a></dd>
                <dt>视图</dt>
                <dd><a href="layouts_and_rendering.html">Rails 布局和视图渲染</a></dd>
                <dd><a href="form_helpers.html">Action View 表单辅助方法</a></dd>
                <dt>控制器</dt>
                <dd><a href="action_controller_overview.html">Action Controller 概览</a></dd>
                <dd><a href="routing.html">Rails 路由全解</a></dd>
              </dl>
              <dl class="R">
                <dt>深入探索</dt>
                <dd><a href="active_support_core_extensions.html">Active Support 核心扩展</a></dd>
                <dd><a href="i18n.html">Rails 国际化 API</a></dd>
                <dd><a href="action_mailer_basics.html">Action Mailer 基础</a></dd>
                <dd><a href="active_job_basics.html">Active Job 基础</a></dd>
                <dd><a href="testing.html">Rails 应用测试指南</a></dd>
                <dd><a href="security.html">Ruby on Rails 安全指南</a></dd>
                <dd><a href="debugging_rails_applications.html">调试 Rails 应用</a></dd>
                <dd><a href="configuring.html">配置 Rails 应用</a></dd>
                <dd><a href="command_line.html">Rails 命令行</a></dd>
                <dd><a href="asset_pipeline.html">Asset Pipeline</a></dd>
                <dd><a href="working_with_javascript_in_rails.html">在 Rails 中使用 JavaScript</a></dd>
                <dd><a href="autoloading_and_reloading_constants.html">自动加载和重新加载常量</a></dd>
                <dd><a href="caching_with_rails.html">Rails 缓存概览</a></dd>
                <dd><a href="api_app.html">使用 Rails 开发只提供 API 的应用</a></dd>
                <dd><a href="action_cable_overview.html">Action Cable 概览</a></dd>
                <dt>扩展 Rails</dt>
                <dd><a href="rails_on_rack.html">Rails on Rack</a></dd>
                <dd><a href="generators.html">创建及定制 Rails 生成器和模板</a></dd>
                <dt>为 Ruby on Rails 做贡献</dt>
                <dd><a href="contributing_to_ruby_on_rails.html">为 Ruby on Rails 做贡献</a></dd>
                <dd><a href="api_documentation_guidelines.html">API 文档指导方针</a></dd>
                <dd><a href="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南指导方针</a></dd>
                <dt>维护方针</dt>
                <dd><a href="maintenance_policy.html">Ruby on Rails 的维护方针</a></dd>
                <dt>发布记</dt>
                <dd><a href="upgrading_ruby_on_rails.html">Ruby on Rails 升级指南</a></dd>
                <dd><a href="5_0_release_notes.html">Ruby on Rails 5.0 发布记</a></dd>
                <dd><a href="4_2_release_notes.html">Ruby on Rails 4.2 发布记</a></dd>
                <dd><a href="4_1_release_notes.html">Ruby on Rails 4.1 发布记</a></dd>
                <dd><a href="4_0_release_notes.html">Ruby on Rails 4.0 发布记</a></dd>
                <dd><a href="3_2_release_notes.html">Ruby on Rails 3.2 发布记</a></dd>
                <dd><a href="3_1_release_notes.html">Ruby on Rails 3.1 发布记</a></dd>
                <dd><a href="3_0_release_notes.html">Ruby on Rails 3.0 发布记</a></dd>
                <dd><a href="2_3_release_notes.html">Ruby on Rails 2.3 发布记</a></dd>
                <dd><a href="2_2_release_notes.html">Ruby on Rails 2.2 发布记</a></dd>
              </dl>
          </div>
        </li>
        <li><a class="nav-item" href="contributing_to_ruby_on_rails.html">贡献</a></li>
        <li><a class="nav-item" href="credits.html">感谢</a></li>
        <li class="guides-index guides-index-small">
          <select class="guides-index-item nav-item">
            <option value="index.html">指南索引</option>
              <optgroup label="新手入门">
                  <option value="getting_started.html">Rails 入门</option>
              </optgroup>
              <optgroup label="模型">
                  <option value="active_record_basics.html">Active Record 基础</option>
                  <option value="active_record_migrations.html">Active Record 迁移</option>
                  <option value="active_record_validations.html">Active Record 数据验证</option>
                  <option value="active_record_callbacks.html">Active Record 回调</option>
                  <option value="association_basics.html">Active Record 关联</option>
                  <option value="active_record_querying.html">Active Record 查询接口</option>
              </optgroup>
              <optgroup label="视图">
                  <option value="layouts_and_rendering.html">Rails 布局和视图渲染</option>
                  <option value="form_helpers.html">Action View 表单辅助方法</option>
              </optgroup>
              <optgroup label="控制器">
                  <option value="action_controller_overview.html">Action Controller 概览</option>
                  <option value="routing.html">Rails 路由全解</option>
              </optgroup>
              <optgroup label="深入探索">
                  <option value="active_support_core_extensions.html">Active Support 核心扩展</option>
                  <option value="i18n.html">Rails 国际化 API</option>
                  <option value="action_mailer_basics.html">Action Mailer 基础</option>
                  <option value="active_job_basics.html">Active Job 基础</option>
                  <option value="testing.html">Rails 应用测试指南</option>
                  <option value="security.html">Ruby on Rails 安全指南</option>
                  <option value="debugging_rails_applications.html">调试 Rails 应用</option>
                  <option value="configuring.html">配置 Rails 应用</option>
                  <option value="command_line.html">Rails 命令行</option>
                  <option value="asset_pipeline.html">Asset Pipeline</option>
                  <option value="working_with_javascript_in_rails.html">在 Rails 中使用 JavaScript</option>
                  <option value="autoloading_and_reloading_constants.html">自动加载和重新加载常量</option>
                  <option value="caching_with_rails.html">Rails 缓存概览</option>
                  <option value="api_app.html">使用 Rails 开发只提供 API 的应用</option>
                  <option value="action_cable_overview.html">Action Cable 概览</option>
              </optgroup>
              <optgroup label="扩展 Rails">
                  <option value="rails_on_rack.html">Rails on Rack</option>
                  <option value="generators.html">创建及定制 Rails 生成器和模板</option>
              </optgroup>
              <optgroup label="为 Ruby on Rails 做贡献">
                  <option value="contributing_to_ruby_on_rails.html">为 Ruby on Rails 做贡献</option>
                  <option value="api_documentation_guidelines.html">API 文档指导方针</option>
                  <option value="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南指导方针</option>
              </optgroup>
              <optgroup label="维护方针">
                  <option value="maintenance_policy.html">Ruby on Rails 的维护方针</option>
              </optgroup>
              <optgroup label="发布记">
                  <option value="upgrading_ruby_on_rails.html">Ruby on Rails 升级指南</option>
                  <option value="5_0_release_notes.html">Ruby on Rails 5.0 发布记</option>
                  <option value="4_2_release_notes.html">Ruby on Rails 4.2 发布记</option>
                  <option value="4_1_release_notes.html">Ruby on Rails 4.1 发布记</option>
                  <option value="4_0_release_notes.html">Ruby on Rails 4.0 发布记</option>
                  <option value="3_2_release_notes.html">Ruby on Rails 3.2 发布记</option>
                  <option value="3_1_release_notes.html">Ruby on Rails 3.1 发布记</option>
                  <option value="3_0_release_notes.html">Ruby on Rails 3.0 发布记</option>
                  <option value="2_3_release_notes.html">Ruby on Rails 2.3 发布记</option>
                  <option value="2_2_release_notes.html">Ruby on Rails 2.2 发布记</option>
              </optgroup>
          </select>
        </li>
      </ul>
    </div>
  </div>
  <hr class="hide" />

  <div id="feature">
    <div class="wrapper">
      <h2>Rails 国际化 API</h2><p>Rails（Rails 2.2 及以上版本）自带的 Ruby I18n（internationalization 的简写）gem，提供了易用、可扩展的框架，用于把应用翻译成英语之外的语言，或为应用提供多语言支持。</p><p>“国际化”（internationalization）过程通常是指，把所有字符串及本地化相关信息（例如日期或货币格式）从应用中抽取出来。“本地化”（localization）过程通常是指，翻译这些字符串并提供相关信息的本地格式。</p><p>因此，在国际化 Rails 应用的过程中，我们需要：</p>
<ul>
<li>  确保 Rails 提供了 I18n 支持；</li>
<li>  把区域设置字典（locale dictionary）的位置告诉 Rails；</li>
<li>  告诉 Rails 如何设置、保存和切换区域（locale）。</li>
</ul>
<p>在本地化 Rails 应用的过程中，我们可能需要完成下面三项工作：</p>
<ul>
<li>  替换或补充 Rails 的默认区域设置，例如日期和时间格式、月份名称、Active Record 模型名等；</li>
<li>  从应用中抽取字符串，并放入字典，例如视图中的闪现信息（flash message）、静态文本等；</li>
<li>  把生成的字典储存在某个地方。</li>
</ul>
<p>本文介绍 Rails I18n API，并提供国际化 Rails 应用的入门教程。</p><p>读完本文后，您将学到：</p>
<ul>
<li>  Rails 中 I18n 的工作原理；</li>
<li>  在 REST 式应用中正确使用 I18n 的几种方式；</li>
<li>  如何使用 I18n 翻译 Active Record 错误或 Action Mailer 电子邮件主题；</li>
<li>  用于进一步翻译应用的其他工具。</li>
</ul>


              <div id="subCol">
          <h3 class="chapter"><img src="images/chapters_icon.gif" alt="" />目录</h3>
          <ol class="chapters">
<li>
<a href="#how-i18n-in-ruby-on-rails-works">Rails 中 I18n 的工作原理</a>

<ul>
<li><a href="#the-overall-architecture-of-the-library">I18n 库的总体架构</a></li>
<li><a href="#the-public-i18n-api">I18n 公开 API</a></li>
</ul>
</li>
<li>
<a href="#setup-the-rails-application-for-internationalization">Rails 应用的国际化设置</a>

<ul>
<li><a href="#configure-the-i18n-module">配置 I18n 模块</a></li>
<li><a href="#managing-the-locale-across-requests">跨请求管理区域设置</a></li>
</ul>
</li>
<li>
<a href="#internationalization-and-localization">国际化和本地化</a>

<ul>
<li><a href="#abstracting-localized-code">抽象本地化代码</a></li>
<li><a href="#providing-translations-for-internationalized-strings">为国际化字符串提供翻译</a></li>
<li><a href="#passing-variables-to-translations">把变量传递给翻译</a></li>
<li><a href="#adding-date-time-formats">添加日期/时间格式</a></li>
<li><a href="#inflection-rules-for-other-locales">其他区域的变形规则</a></li>
<li><a href="#localized-views">本地化视图</a></li>
<li><a href="#organization-of-locale-files">区域设置文件的组织</a></li>
</ul>
</li>
<li>
<a href="#overview-of-the-i18n-api-features">I18n API 功能概述</a>

<ul>
<li><a href="#looking-up-translations">查找翻译</a></li>
<li><a href="#pluralization">复数转换</a></li>
<li><a href="#setting-and-passing-a-locale">区域的设置和传递</a></li>
<li><a href="#using-safe-html-translations">使用安全 HTML 翻译</a></li>
<li><a href="#translations-for-active-record-models">Active Record 模型的翻译</a></li>
<li><a href="#translations-for-action-mailer-e-mail-subjects">Action Mailer 电子邮件主题的翻译</a></li>
<li><a href="#overview-of-other-built-in-methods-that-provide-i18n-support">提供 I18n 支持的其他内置方法概述</a></li>
</ul>
</li>
<li><a href="#how-to-store-your-custom-translations">如何储存自定义翻译</a></li>
<li>
<a href="#customize-your-i18n-setup">自定义 I18n 设置</a>

<ul>
<li><a href="#using-different-backends">使用不同的后端</a></li>
<li><a href="#using-different-exception-handlers">使用不同的异常处理程序</a></li>
</ul>
</li>
<li><a href="#conclusion">结论</a></li>
<li><a href="#contributing-to-rails-i18n">为 Rails I18n 作贡献</a></li>
<li><a href="#resources">资源</a></li>
<li><a href="#authors">作者</a></li>
</ol>

        </div>

    </div>
  </div>

  <div id="container">
    <div class="wrapper">
      <div id="mainCol">
        <div class="note"><p>Ruby I18n 框架提供了 Rails 应用国际化/本地化所需的全部必要支持。我们还可以使用各种 gem 来添加附加功能或特性。更多介绍请参阅 <a href="https://github.com/svenfuchs/rails-i18n">rails-18n gem</a>。</p></div><p><a class="anchor" id="how-i18n-in-ruby-on-rails-works"></a></p><h3 id="how-i18n-in-ruby-on-rails-works">1 Rails 中 I18n 的工作原理</h3><p>国际化是一个复杂的问题。自然语言在很多方面（例如复数规则）有所不同，要想一次性提供解决所有问题的工具很难。因此，Rails I18n API 专注于：</p>
<ul>
<li>  支持英语及类似语言</li>
<li>  易于定制和扩展，以支持其他语言</li>
</ul>
<p>作为这个解决方案的一部分，Rails 框架中的每个静态字符串（例如，Active Record 数据验证信息、时间和日期格式）都已国际化。Rails 应用的本地化意味着把这些静态字符串翻译为所需语言。</p><p><a class="anchor" id="the-overall-architecture-of-the-library"></a></p><h4 id="the-overall-architecture-of-the-library">1.1 I18n 库的总体架构</h4><p>因此，Ruby I18n gem 分为两部分：</p>
<ul>
<li>  I18n 框架的公开 API——包含公开方法的 Ruby 模块，定义 I18n 库的工作方式</li>
<li>  实现这些方法的默认后端（称为简单后端）</li>
</ul>
<p>作为用户，我们应该始终只访问 I18n 模块的公开方法，但了解后端的功能也很有帮助。</p><div class="note"><p>我们可以把默认的简单后端替换为其他功能更强的后端，这时翻译数据可能会储存在关系数据库、GetText 字典或类似解决方案中。更多介绍请参阅 <a href="#using-different-backends">使用不同的后端</a>。</p></div><p><a class="anchor" id="the-public-i18n-api"></a></p><h4 id="the-public-i18n-api">1.2 I18n 公开 API</h4><p>I18n API 中最重要的两个方法是：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
translate # 查找文本翻译
localize  # 把日期和时间对象转换为本地格式（本地化）

</pre>
</div>
<p>这两个方法的别名分别为 <code>#t</code> 和 <code>#l</code>，用法如下：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
I18n.t 'store.title'
I18n.l Time.now

</pre>
</div>
<p>对于下列属性，I18n API 还提供了属性读值方法和设值方法：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
load_path                 # 自定义翻译文件的路径
locale                    # 获取或设置当前区域
default_locale            # 获取或设置默认区域
available_locales         # 应用可用的区域设置白名单
enforce_available_locales # 强制使用白名单（true 或 false）
exception_handler         # 使用其他异常处理程序
backend                   # 使用其他后端

</pre>
</div>
<p>现在，我们已经掌握了 Rails I18n API 的基本用法，从下一节开始，我们将从头开始国际化一个简单的 Rails 应用。</p><p><a class="anchor" id="setup-the-rails-application-for-internationalization"></a></p><h3 id="setup-the-rails-application-for-internationalization">2 Rails 应用的国际化设置</h3><p>本节介绍为 Rails 应用提供 I18n 支持的几个步骤。</p><p><a class="anchor" id="configure-the-i18n-module"></a></p><h4 id="configure-the-i18n-module">2.1 配置 I18n 模块</h4><p>根据“多约定，少配置”原则，Rails I18n 库提供了默认翻译字符串。如果需要不同的翻译字符串，可以直接覆盖默认值。</p><p>Rails 会把 <code>config/locales</code> 文件夹中的 <code>.rb</code> 和 <code>.yml</code> 文件自动添加到翻译文件加载路径中。</p><p>这个文件夹中的 <code>en.yml</code> 区域设置文件包含了一个翻译字符串示例：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
en:
  hello: "Hello world"

</pre>
</div>
<p>上面的代码表示，在 <code>:en</code> 区域设置中，键 <code>hello</code> 会映射到 <code>Hello world</code> 字符串上。在 Rails 中，字符串都以这种方式进行国际化，例如，Active Model 的数据验证信息位于 <a href="https://github.com/rails/rails/blob/master/activemodel/lib/active_model/locale/en.yml">activemodel/lib/active_model/locale/en.yml</a> 文件中，时间和日期格式位于 <a href="https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml">activesupport/lib/active_support/locale/en.yml</a> 文件中。我们可以使用 YAML 或标准 Ruby 散列，把翻译信息储存在默认的简单后端中。</p><p>I18n 库使用英语作为默认的区域设置，例如，如果未设置为其他区域，那就使用 <code>:en</code> 区域来查找翻译。</p><div class="note"><p>经过<a href="http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en">讨论</a>，I18n 库在选取区域设置的键时最终采取了务实的方式，也就是仅包含语言部分，例如 <code>:en</code>、<code>:pl</code>，而不是传统上使用的语言和区域两部分，例如 <code>:en-US</code> 、 <code>:en-GB</code>。很多国际化的应用都是这样做的，例如把 <code>:cs</code>、<code>:th</code> 和 <code>:es</code> 分别用于捷克语、泰语和西班牙语。尽管如此，在同一语系中也可能存在重要的区域差异，例如，<code>:en-US</code> 使用 <code>$</code> 作为货币符号，而 <code>:en-GB</code> 使用 <code>£</code> 作为货币符号。因此，如果需要，我们也可以使用传统方式，例如，在 <code>:en-GB</code> 字典中提供完整的 <code>"English - United Kingdom"</code> 区域。像 <a href="https://github.com/globalize/globalize">Globalize3</a> 这样的 gem 可以实现这一功能。</p></div><p>Rails 会自动加载翻译文件加载路径（<code>I18n.load_path</code>），这是一个保存有翻译文件路径的数组。通过配置翻译文件加载路径，我们可以自定义翻译文件的目录结构和文件命名规则。</p><div class="note"><p>I18n 库的后端采用了延迟加载技术，相关翻译信息仅在第一次查找时加载。我们可以根据需要，随时替换默认后端。</p></div><p>默认的区域设置和翻译的加载路径可以在 <code>config/application.rb</code> 文件中配置，如下所示：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
config.i18n.default_locale = :de

</pre>
</div>
<p>在查找翻译文件之前，必须先指定翻译文件加载路径。应该通过初始化脚本修改默认区域设置，而不是 <code>config/application.rb</code> 文件：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# config/initializers/locale.rb

# 指定 I18n 库搜索翻译文件的路径
I18n.load_path += Dir[Rails.root.join('lib', 'locale', '*.{rb,yml}')]

# 应用可用的区域设置白名单
I18n.available_locales = [:en, :pt]

# 修改默认区域设置（默认是 :en）
I18n.default_locale = :pt

</pre>
</div>
<p><a class="anchor" id="managing-the-locale-across-requests"></a></p><h4 id="managing-the-locale-across-requests">2.2 跨请求管理区域设置</h4><p>除非显式设置了 <code>I18n.locale</code>，默认区域设置将会应用于所有翻译文件。</p><p>本地化应用有时需要支持多区域设置。此时，需要在每个请求之前设置区域，这样在请求的整个生命周期中，都会根据指定区域，对所有字符串进行翻译。</p><p>我们可以在 <code>ApplicationController</code> 中使用 <code>before_action</code> 方法设置区域：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
before_action :set_locale

def set_locale
  I18n.locale = params[:locale] || I18n.default_locale
end

</pre>
</div>
<p>上面的例子说明了如何使用 URL 查询参数来设置区域。例如，对于 <a href="http://example.com/books?locale=pt">http://example.com/books?locale=pt</a> 会使用葡萄牙语进行本地化，对于 <a href="http://localhost:3000?locale=de">http://localhost:3000?locale=de</a> 会使用德语进行本地化。</p><p>接下来介绍区域设置的几种不同方式。</p><p><a class="anchor" id="setting-the-locale-from-the-domain-name"></a></p><h5 id="setting-the-locale-from-the-domain-name">2.2.1 根据域名设置区域</h5><p>第一种方式是，根据应用的域名设置区域。例如，通过 <code>www.example.com</code> 加载英语（或默认）区域设置，通过 <code>www.example.es</code> 加载西班牙语区域设置。也就是根据顶级域名设置区域。这种方式有下列优点：</p>
<ul>
<li>  区域设置成为 URL 地址显而易见的一部分</li>
<li>  用户可以直观地判断出页面所使用的语言</li>
<li>  在 Rails 中非常容易实现</li>
<li>  搜索引擎偏爱这种把不同语言内容放在不同域名上的做法</li>
</ul>
<p>在 <code>ApplicationController</code> 中，我们可以进行如下配置：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
before_action :set_locale

def set_locale
  I18n.locale = extract_locale_from_tld || I18n.default_locale
end

# 从顶级域名中获取区域设置，如果获取失败会返回 nil
# 需要在 /etc/hosts 文件中添加如下设置：
#   127.0.0.1 application.com
#   127.0.0.1 application.it
#   127.0.0.1 application.pl
def extract_locale_from_tld
  parsed_locale = request.host.split('.').last
  I18n.available_locales.map(&amp;:to_s).include?(parsed_locale) ? parsed_locale : nil
end

</pre>
</div>
<p>我们还可以通过类似方式，根据子域名设置区域：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# 从子域名中获取区域设置（例如 http://it.application.local:3000）
# 需要在 /etc/hosts 文件中添加如下设置：
#   127.0.0.1 gr.application.local
def extract_locale_from_subdomain
  parsed_locale = request.subdomains.first
  I18n.available_locales.map(&amp;:to_s).include?(parsed_locale) ? parsed_locale : nil
end

</pre>
</div>
<p>要想为应用添加区域设置切换菜单，可以使用如下代码：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
link_to("Deutsch", "#{APP_CONFIG[:deutsch_website_url]}#{request.env['PATH_INFO']}")

</pre>
</div>
<p>其中 <code>APP_CONFIG[:deutsch_website_url]</code> 的值类似 <code>http://www.application.de</code>。</p><p>尽管这个解决方案具有上面提到的各种优点，但通过不同域名来提供不同的本地化版本（“语言版本”）有时并非我们的首选。在其他各种可选方案中，在 URL 参数（或请求路径）中包含区域设置是最常见的。</p><p><a class="anchor" id="setting-the-locale-from-url-params"></a></p><h5 id="setting-the-locale-from-url-params">2.2.2 根据 URL 参数设置区域</h5><p>区域设置（和传递）的最常见方式，是将其包含在 URL 参数中，例如，在前文第一个示例中，<code>before_action</code> 方法调用中的 <code>I18n.locale = params[:locale]</code>。此时，我们会使用 <code>www.example.com/books?locale=ja</code> 或 <code>www.example.com/ja/books</code> 这样的网址。</p><p>和根据域名设置区域类似，这种方式具有不少优点，尤其是 REST 式的命名风格，顺应了当前的互联网潮流。不过采用这种方式所需的工作量要大一些。</p><p>从 URL 参数获取并设置区域并不难，只要把区域设置包含在 URL 中并通过请求传递即可。当然，没有人愿意在生成每个 URL 地址时显式添加区域设置，例如 <code>link_to(books_url(locale: I18n.locale))</code>。</p><p>Rails 的 <code>ApplicationController#default_url_options</code> 方法提供的“集中修改 URL 动态生成规则”的功能，正好可以解决这个问题：我们可以设置 <code>url_for</code> 及相关辅助方法的默认行为（通过覆盖 <code>default_url_options</code> 方法）。</p><p>我们可以在 <code>ApplicationController</code> 中添加下面的代码：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# app/controllers/application_controller.rb
def default_url_options
  { locale: I18n.locale }
end

</pre>
</div>
<p>这样，所有依赖于 <code>url_for</code> 的辅助方法（例如，具名路由辅助方法 <code>root_path</code> 和 <code>root_url</code>，资源路由辅助方法 <code>books_path</code> 和 <code>books_url</code> 等等）都会自动在查询字符串中添加区域设置，例如：<code>http://localhost:3001/?locale=ja</code>。</p><p>至此，我们也许已经很满意了。但是，在应用的每个 URL 地址的末尾添加区域设置，会影响 URL 地址的可读性。此外，从架构的角度看，区域设置的层级应该高于 URL 地址中除域名之外的其他组成部分，这一点也应该通过 URL 地址自身体现出来。</p><p>要想使用 <code>http://www.example.com/en/books</code>（加载英语区域设置）和 <code>http://www.example.com/nl/books</code>（加载荷兰语区域设置）这样的 URL 地址，我们可以使用前文提到的覆盖 <code>default_url_options</code> 方法的方式，通过 <code>scope</code> 方法设置路由：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# config/routes.rb
scope "/:locale" do
  resources :books
end

</pre>
</div>
<p>现在，当我们调用 <code>books_path</code> 方法时，就会得到 <code>"/en/books"</code>（对于默认区域设置）。像 <code>http://localhost:3001/nl/books</code> 这样的 URL 地址会加载荷兰语区域设置，之后调用 <code>books_path</code> 方法时会返回 <code>"/nl/books"</code>（因为区域设置发生了变化）。</p><div class="warning"><p>由于 <code>default_url_options</code> 方法的返回值是根据请求分别缓存的，因此无法通过循环调用辅助方法来生成 URL 地址中的区域设置，
也就是说，无法在每次迭代中设置相应的 <code>I18n.locale</code>。正确的做法是，保持 <code>I18n.locale</code> 不变，向辅助方法显式传递 <code>:locale</code> 选项，或者编辑 <code>request.original_fullpath</code>。</p></div><p>如果不想在路由中强制使用区域设置，我们可以使用可选的路径作用域（用括号表示），就像下面这样：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# config/routes.rb
scope "(:locale)", locale: /en|nl/ do
  resources :books
end

</pre>
</div>
<p>通过这种方式，访问不带区域设置的 <code>http://localhost:3001/books</code> URL 地址时就不会抛出 <code>Routing Error</code> 错误了。这样，我们就可以在不指定区域设置时，使用默认的区域设置。</p><p>当然，我们需要特别注意应用的根地址﹝通常是“主页（homepage）”或“仪表盘（dashboard）”﹞。像 <code>root to: "books#index"</code> 这样的不考虑区域设置的路由声明，会导致 <code>http://localhost:3001/nl</code> 无法正常访问。（尽管“只有一个根地址”看起来并没有错）</p><p>因此，我们可以像下面这样映射 URL 地址：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# config/routes.rb
get '/:locale' =&gt; 'dashboard#index'

</pre>
</div>
<p>需要特别注意路由的声明顺序，以避免这条路由覆盖其他路由。（我们可以把这条路由添加到 <code>root :to</code> 路由声明之前）</p><div class="note"><p>有一些 gem 可以简化路由设置，如 <a href="https://github.com/svenfuchs/routing-filter/tree/master">routing_filter</a>、<a href="https://github.com/francesc/rails-translate-routes">rails-translate-routes</a> 和 <a href="https://github.com/enriclluelles/route_translator">route_translator</a>。</p></div><p><a class="anchor" id="setting-the-locale-from-user-preferences"></a></p><h5 id="setting-the-locale-from-user-preferences">2.2.3 根据用户偏好设置进行区域设置</h5><p>支持用户身份验证的应用，可能会允许用户在界面中选择区域偏好设置。通过这种方式，用户选择的区域偏好设置会储存在数据库中，并用于处理该用户发起的请求。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def set_locale
  I18n.locale = current_user.try(:locale) || I18n.default_locale
end

</pre>
</div>
<p><a class="anchor" id="choosing-an-implied-locale"></a></p><h5 id="choosing-an-implied-locale">2.2.4 使用隐式区域设置</h5><p>如果没有显式地为请求设置区域（例如，通过上面提到的各种方式），应用就会尝试推断出所需区域。</p><p><a class="anchor" id="inferring-locale-from-the-language-header"></a></p><h6 id="inferring-locale-from-the-language-header">2.2.4.1 根据 HTTP 首部推断区域设置</h6><p><code>Accept-Language</code> HTTP 首部指明响应请求时使用的首选语言。浏览器<a href="http://www.w3.org/International/questions/qa-lang-priorities">根据用户的语言偏好设置设定这个 HTTP 首部</a>，这是推断区域设置的首选方案。</p><p>下面是使用 <code>Accept-Language</code> HTTP 首部的一个简单实现：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def set_locale
  logger.debug "* Accept-Language: #{request.env['HTTP_ACCEPT_LANGUAGE']}"
  I18n.locale = extract_locale_from_accept_language_header
  logger.debug "* Locale set to '#{I18n.locale}'"
end

private
  def extract_locale_from_accept_language_header
    request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first
  end

</pre>
</div>
<p>实际上，我们通常会使用更可靠的代码。Iain Hecker 开发的 <a href="https://github.com/iain/http_accept_language/tree/master">http_accept_language</a> 或 Ryan Tomayko 开发的 <a href="https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/locale.rb">locale</a> Rack 中间件就提供了更好的解决方案。</p><p><a class="anchor" id="inferring-the-locale-from-ip-geolocation"></a></p><h6 id="inferring-the-locale-from-ip-geolocation">2.2.4.2 根据 IP 地理位置推断区域设置</h6><p>我们可以通过客户端请求的 IP 地址来推断客户端所处的地理位置，进而推断其区域设置。<a href="http://www.maxmind.com/app/geolitecountry">GeoIP Lite Country</a> 这样的服务或 <a href="https://github.com/alexreisner/geocoder">geocoder</a> 这样的 gem 就可以实现这一功能。</p><p>一般来说，这种方式远不如使用 HTTP 首部可靠，因此并不适用于大多数 Web 应用。</p><p><a class="anchor" id="storing-the-locale-from-the-session-or-cookies"></a></p><h5 id="storing-the-locale-from-the-session-or-cookies">2.2.5 在会话或 Cookie 中储存区域设置</h5><div class="warning"><p>我们可能会认为，可以把区域设置储存在会话或 Cookie 中。但是，我们不能这样做。区域设置应该是透明的，并作为 URL 地址的一部分。这样，我们就不会打破用户的正常预期：如果我们发送一个 URL 地址给朋友，他们应该看到和我们一样的页面和内容。这就是所谓的 REST 规则。关于 REST 规则的更多介绍，请参阅 <a href="http://www.infoq.com/articles/rest-introduction">Stefan Tilkov 写的系列文章</a>。后文将讨论这个规则的一些例外情况。</p></div><p><a class="anchor" id="internationalization-and-localization"></a></p><h3 id="internationalization-and-localization">3 国际化和本地化</h3><p>现在，我们已经完成了对 Rails 应用 I18n 支持的初始化，进行了区域设置，并在不同请求中应用了区域设置。</p><p>接下来，我们要通过抽象本地化相关元素，完成应用的国际化。最后，通过为这些抽象元素提供必要翻译，完成应用的本地化。</p><p>下面给出一个例子：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# config/routes.rb
Rails.application.routes.draw do
  root to: "home#index"
end

</pre>
</div>
<div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# app/controllers/application_controller.rb
class ApplicationController &lt; ActionController::Base
  before_action :set_locale

  def set_locale
    I18n.locale = params[:locale] || I18n.default_locale
  end
end

</pre>
</div>
<div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# app/controllers/home_controller.rb
class HomeController &lt; ApplicationController
  def index
    flash[:notice] = "Hello Flash"
  end
end

</pre>
</div>
<div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
# app/views/home/index.html.erb
&lt;h1&gt;Hello World&lt;/h1&gt;
&lt;p&gt;&lt;%= flash[:notice] %&gt;&lt;/p&gt;

</pre>
</div>
<p><img src="images/i18n/demo_untranslated.png" alt="demo untranslated"></p><p><a class="anchor" id="abstracting-localized-code"></a></p><h4 id="abstracting-localized-code">3.1 抽象本地化代码</h4><p>在我们的代码中有两个英文字符串（<code>"Hello Flash"</code> 和 <code>"Hello World"</code>），它们在响应用户请求时显示。为了国际化这部分代码，需要用 Rails 提供的 <code>#t</code> 辅助方法来代替这两个字符串，同时为每个字符串选择合适的键：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# app/controllers/home_controller.rb
class HomeController &lt; ApplicationController
  def index
    flash[:notice] = t(:hello_flash)
  end
end

</pre>
</div>
<div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
# app/views/home/index.html.erb
&lt;h1&gt;&lt;%= t :hello_world %&gt;&lt;/h1&gt;
&lt;p&gt;&lt;%= flash[:notice] %&gt;&lt;/p&gt;

</pre>
</div>
<p>现在，Rails 在渲染 <code>index</code> 视图时会显示错误信息，告诉我们缺少 <code>:hello_world</code> 和 <code>:hello_flash</code> 这两个键的翻译。</p><p><img src="images/i18n/demo_translation_missing.png" alt="demo translation missing"></p><div class="note"><p>Rails 为视图添加了 <code>t</code>（<code>translate</code>）辅助方法，从而避免了反复使用 <code>I18n.t</code> 这么长的写法。此外，<code>t</code> 辅助方法还能捕获缺少翻译的错误，把生成的错误信息放在 <code>&lt;span class="translation_missing"&gt;</code> 元素里。</p></div><p><a class="anchor" id="providing-translations-for-internationalized-strings"></a></p><h4 id="providing-translations-for-internationalized-strings">3.2 为国际化字符串提供翻译</h4><p>下面，我们把缺少的翻译添加到翻译字典文件中：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
# config/locales/en.yml
en:
  hello_world: Hello world!
  hello_flash: Hello flash!

# config/locales/pirate.yml
pirate:
  hello_world: Ahoy World
  hello_flash: Ahoy Flash

</pre>
</div>
<p>因为我们没有修改 <code>default_locale</code>，翻译会使用 <code>:en</code> 区域设置，响应请求时生成的视图会显示英文字符串：</p><p><img src="images/i18n/demo_translated_en.png" alt="demo translated en"></p><p>如果我们通过 URL 地址（<code>http://localhost:3000?locale=pirate</code>）把区域设置为 <code>pirate</code>，响应请求时生成的视图就会显示海盗黑话：</p><p><img src="images/i18n/demo_translated_pirate.png" alt="demo translated pirate"></p><div class="note"><p>添加新的区域设置文件后，需要重启服务器。</p></div><p>要想把翻译储存在 SimpleStore 中，我们可以使用 YAML（<code>.yml</code>）或纯 Ruby（<code>.rb</code>）文件。大多数 Rails 开发者会优先选择 YAML。不过 YAML 有一个很大的缺点，它对空格和特殊字符非常敏感，因此有可能出现应用无法正确加载字典的情况。而 Ruby 文件如果有错误，在第一次加载时应用就会崩溃，因此我们很容易就能找出问题。（如果在使用 YAML 字典时遇到了“奇怪的问题”，可以尝试把字典的相关部分放入 Ruby 文件中。）</p><p>如果翻译存储在 YAML 文件中，有些键必须转义：</p>
<ul>
<li>  true, on, yes</li>
<li>  false, off, no</li>
</ul>
<p>例如：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
# config/locales/en.yml
en:
  success:
    'true':  'True!'
    'on':    'On!'
    'false': 'False!'
  failure:
    true:    'True!'
    off:     'Off!'
    false:   'False!'

</pre>
</div>
<div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
I18n.t 'success.true' # =&gt; 'True!'
I18n.t 'success.on' # =&gt; 'On!'
I18n.t 'success.false' # =&gt; 'False!'
I18n.t 'failure.false' # =&gt; Translation Missing
I18n.t 'failure.off' # =&gt; Translation Missing
I18n.t 'failure.true' # =&gt; Translation Missing

</pre>
</div>
<p><a class="anchor" id="passing-variables-to-translations"></a></p><h4 id="passing-variables-to-translations">3.3 把变量传递给翻译</h4><p>成功完成应用国际化的一个关键因素是，避免在抽象本地化代码时，对语法规则做出不正确的假设。某个区域设置的基本语法规则，在另一个区域设置中可能不成立。</p><p>下面给出一个不正确抽象的例子，其中对翻译的不同组成部分的排序进行了假设。注意，为了处理这个例子中出现的情况，Rails 提供了 <code>number_to_currency</code> 辅助方法。</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
# app/views/products/show.html.erb
&lt;%= "#{t('currency')}#{@product.price}" %&gt;

</pre>
</div>
<div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
# config/locales/en.yml
en:
  currency: "$"

# config/locales/es.yml
es:
  currency: "€"

</pre>
</div>
<p>如果产品价格是 10，那么西班牙语的正确翻译是“10 €”而不是“€10”，但上面的抽象并不能正确处理这种情况。</p><p>为了创建正确的抽象，I18n gem 提供了变量插值（variable interpolation）功能，它允许我们在翻译定义（translation definition）中使用变量，并把这些变量的值传递给翻译方法。</p><p>下面给出一个正确抽象的例子：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
# app/views/products/show.html.erb
&lt;%= t('product_price', price: @product.price) %&gt;

</pre>
</div>
<div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
# config/locales/en.yml
en:
  product_price: "$%{price}"

# config/locales/es.yml
es:
  product_price: "%{price} €"

</pre>
</div>
<p>所有的语法和标点都由翻译定义自己决定，所以抽象可以给出正确的翻译。</p><div class="note"><p><code>default</code> 和 <code>scope</code> 是保留关键字，不能用作变量名。如果误用，Rails 会抛出 <code>I18n::ReservedInterpolationKey</code> 异常。如果没有把翻译所需的插值变量传递给 <code>#translate</code> 方法，Rails 会抛出 <code>I18n::MissingInterpolationArgument</code> 异常。</p></div><p><a class="anchor" id="adding-date-time-formats"></a></p><h4 id="adding-date-time-formats">3.4 添加日期/时间格式</h4><p>现在，我们要给视图添加时间戳，以便演示日期/时间的本地化功能。要想本地化时间格式，可以把时间对象传递给 <code>I18n.l</code> 方法或者（最好）使用 <code>#l</code> 辅助方法。可以通过 <code>:format</code> 选项指定时间格式（默认情况下使用 <code>:default</code> 格式）。</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
# app/views/home/index.html.erb
&lt;h1&gt;&lt;%=t :hello_world %&gt;&lt;/h1&gt;
&lt;p&gt;&lt;%= flash[:notice] %&gt;&lt;/p&gt;
&lt;p&gt;&lt;%= l Time.now, format: :short %&gt;&lt;/p&gt;

</pre>
</div>
<p>然后在 <code>pirate</code> 翻译文件中添加时间格式（Rails 默认使用的英文翻译文件已经包含了时间格式）：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
# config/locales/pirate.yml
pirate:
  time:
    formats:
      short: "arrrround %H'ish"

</pre>
</div>
<p>得到的结果如下：</p><p><img src="images/i18n/demo_localized_pirate.png" alt="demo localized pirate"></p><div class="info"><p>现在，我们可能需要添加一些日期/时间格式，这样 I18n 后端才能按照预期工作（至少应该为 <code>pirate</code> 区域设置添加日期/时间格式）。当然，很可能已经有人通过翻译 Rails 相关区域设置的默认值，完成了这些工作。<a href="https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale">GitHub 上的 rails-i18n 仓库</a>提供了各种本地化文件的存档。把这些本地化文件放在 <code>config/locales/</code> 文件夹中即可正常使用。</p></div><p><a class="anchor" id="inflection-rules-for-other-locales"></a></p><h4 id="inflection-rules-for-other-locales">3.5 其他区域的变形规则</h4><p>Rails 允许我们为英语之外的区域定义变形规则（例如单复数转换规则）。在 <code>config/initializers/inflections.rb</code> 文件中，我们可以为多个区域定义规则。这个初始化脚本包含了为英语指定附加规则的例子，我们可以参考这些例子的格式为其他区域定义规则。</p><p><a class="anchor" id="localized-views"></a></p><h4 id="localized-views">3.6 本地化视图</h4><p>假设应用中包含 <code>BooksController</code>，<code>index</code> 动作默认会渲染 <code>app/views/books/index.html.erb</code> 模板。如果我们在同一个文件夹中创建了包含本地化变量的 <code>index.es.html.erb</code> 模板，当区域设置为 <code>:es</code> 时，<code>index</code> 动作就会渲染这个模板，而当区域设置为默认区域时， <code>index</code> 动作会渲染通用的 <code>index.html.erb</code> 模板。（在 Rails 的未来版本中，本地化的这种自动化魔术，有可能被应用于 <code>public</code> 文件夹中的资源）</p><p>本地化视图功能很有用，例如，如果我们有大量静态内容，就可以使用本地化视图，从而避免把所有东西都放进 YAML 或 Ruby 字典里的麻烦。但要记住，一旦我们需要修改模板，就必须对每个模板文件逐一进行修改。</p><p><a class="anchor" id="organization-of-locale-files"></a></p><h4 id="organization-of-locale-files">3.7 区域设置文件的组织</h4><p>当我们使用 I18n 库自带的 SimpleStore 时，字典储存在磁盘上的纯文本文件中。对于每个区域，把应用的各部分翻译都放在一个文件中，可能会带来管理上的困难。因此，把每个区域的翻译放在多个文件中，分层进行管理是更好的选择。</p><p>例如，我们可以像下面这样组织 <code>config/locales</code> 文件夹：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
|-defaults
|---es.rb
|---en.rb
|-models
|---book
|-----es.rb
|-----en.rb
|-views
|---defaults
|-----es.rb
|-----en.rb
|---books
|-----es.rb
|-----en.rb
|---users
|-----es.rb
|-----en.rb
|---navigation
|-----es.rb
|-----en.rb

</pre>
</div>
<p>这样，我们就可以把模型和属性名同视图中的文本分离，同时还能使用“默认值”（例如日期和时间格式）。I18n 库的不同后端可以提供不同的分离方式。</p><div class="note"><p>Rails 默认的区域设置加载机制，无法自动加载上面例子中位于嵌套文件夹中的区域设置文件。因此，我们还需要进行显式设置：</p></div><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# config/application.rb
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]

</pre>
</div>
<p><a class="anchor" id="overview-of-the-i18n-api-features"></a></p><h3 id="overview-of-the-i18n-api-features">4 I18n API 功能概述</h3><p>现在我们已经对 I18n 库有了较好的了解，知道了如何国际化简单的 Rails 应用。在下面几个小节中，我们将更深入地了解相关功能。</p><p>这几个小节将展示使用 <code>I18n.translate</code> 方法以及 <code>translate</code> 视图辅助方法的示例（注意视图辅助方法提供的附加功能）。</p><p>所涉及的功能如下：</p>
<ul>
<li>  查找翻译</li>
<li>  把数据插入翻译中</li>
<li>  复数的翻译</li>
<li>  使用安全 HTML 翻译（只针对视图辅助方法）</li>
<li>  本地化日期、数字、货币等</li>
</ul>
<p><a class="anchor" id="looking-up-translations"></a></p><h4 id="looking-up-translations">4.1 查找翻译</h4><p><a class="anchor" id="basic-lookup-scopes-and-nested-keys"></a></p><h5 id="basic-lookup-scopes-and-nested-keys">4.1.1 基本查找、作用域和嵌套键</h5><p>Rails 通过键来查找翻译，其中键可以是符号或字符串。这两种键是等价的，例如：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
I18n.t :message
I18n.t 'message'

</pre>
</div>
<p><code>translate</code> 方法接受 <code>:scope</code> 选项，选项的值可以包含一个或多个附加键，用于指定翻译键（translation key）的“命名空间”或作用域：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
I18n.t :record_invalid, scope: [:activerecord, :errors, :messages]

</pre>
</div>
<p>上述代码会在 Active Record 错误信息中查找 <code>:record_invalid</code> 信息。</p><p>此外，我们还可以用点号分隔的键来指定翻译键和作用域：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
I18n.translate "activerecord.errors.messages.record_invalid"

</pre>
</div>
<p>因此，下列调用是等效的：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
I18n.t 'activerecord.errors.messages.record_invalid'
I18n.t 'errors.messages.record_invalid', scope: :activerecord
I18n.t :record_invalid, scope: 'activerecord.errors.messages'
I18n.t :record_invalid, scope: [:activerecord, :errors, :messages]

</pre>
</div>
<p><a class="anchor" id="defaults"></a></p><h5 id="defaults">4.1.2 默认值</h5><p>如果指定了 <code>:default</code> 选项，在缺少翻译的情况下，就会返回该选项的值：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
I18n.t :missing, default: 'Not here'
# =&gt; 'Not here'

</pre>
</div>
<p>如果 <code>:default</code> 选项的值是符号，这个值会被当作键并被翻译。我们可以为 <code>:default</code> 选项指定多个值，第一个被成功翻译的键或遇到的字符串将被作为返回值。</p><p>例如，下面的代码首先尝试翻译 <code>:missing</code> 键，然后是 <code>:also_missing</code> 键。由于两次翻译都不能得到结果，最后会返回 <code>"Not here"</code> 字符串。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
I18n.t :missing, default: [:also_missing, 'Not here']
# =&gt; 'Not here'

</pre>
</div>
<p><a class="anchor" id="bulk-and-namespace-lookup"></a></p><h5 id="bulk-and-namespace-lookup">4.1.3 批量查找和命名空间查找</h5><p>要想一次查找多个翻译，我们可以传递键的数组作为参数：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
I18n.t [:odd, :even], scope: 'errors.messages'
# =&gt; ["must be odd", "must be even"]

</pre>
</div>
<p>此外，键可以转换为一组翻译的（可能是嵌套的）散列。例如，下面的代码可以生成所有 Active Record 错误信息的散列：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
I18n.t 'activerecord.errors.messages'
# =&gt; {:inclusion=&gt;"is not included in the list", :exclusion=&gt; ... }

</pre>
</div>
<p><a class="anchor" id="lazy-lookup"></a></p><h5 id="lazy-lookup">4.1.4 惰性查找</h5><p>Rails 实现了一种在视图中查找区域设置的便捷方法。如果有下述字典：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
es:
  books:
    index:
      title: "Título"

</pre>
</div>
<p>我们就可以像下面这样在 <code>app/views/books/index.html.erb</code> 模板中查找 <code>books.index.title</code> 的值（注意点号）：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;%= t '.title' %&gt;

</pre>
</div>
<div class="note"><p>只有 <code>translate</code> 视图辅助方法支持根据片段自动补全翻译作用域的功能。</p></div><p>我们还可以在控制器中使用惰性查找（lazy lookup）：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
en:
  books:
    create:
      success: Book created!

</pre>
</div>
<p>用于设置闪现信息：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class BooksController &lt; ApplicationController
  def create
    # ...
    redirect_to books_url, notice: t('.success')
  end
end

</pre>
</div>
<p><a class="anchor" id="pluralization"></a></p><h4 id="pluralization">4.2 复数转换</h4><p>在英语中，一个字符串只有一种单数形式和一种复数形式，例如，“1 message”和“2 messages”。其他语言（<a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ar">阿拉伯语</a>、<a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ja">日语</a>、<a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ru">俄语</a>等）则具有不同的语法，有更多或更少的<a href="http://cldr.unicode.org/index/cldr-spec/plural-rules">复数形式</a>。因此，I18n API 提供了灵活的复数转换功能。</p><p><code>:count</code> 插值变量具有特殊作用，既可以把它插入翻译，又可以用于从翻译中选择复数形式（根据 CLDR 定义的复数转换规则）：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
I18n.backend.store_translations :en, inbox: {
  zero: 'no messages', # 可选
  one: 'one message',
  other: '%{count} messages'
}
I18n.translate :inbox, count: 2
# =&gt; '2 messages'

I18n.translate :inbox, count: 1
# =&gt; 'one message'

I18n.translate :inbox, count: 0
# =&gt; 'no messages'

</pre>
</div>
<p><code>:en</code> 区域设置的复数转换算法非常简单：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
lookup_key = :zero if count == 0 &amp;&amp; entry.has_key?(:zero)
lookup_key ||= count == 1 ? :one : :other
entry[lookup_key]

</pre>
</div>
<p>也就是说，<code>:one</code> 标记的是单数，<code>:other</code> 标记的是复数。如果数量为零，而且有 <code>:zero</code> 元素，用它的值，而不用 <code>:other</code> 的值。</p><p>如果查找键没能返回可转换为复数形式的散列，就会引发 <code>I18n::InvalidPluralizationData</code> 异常。</p><p><a class="anchor" id="setting-and-passing-a-locale"></a></p><h4 id="setting-and-passing-a-locale">4.3 区域的设置和传递</h4><p>区域设置可以伪全局地设置为 <code>I18n.locale</code>（使用 <code>Thread.current</code>，例如 <code>Time.zone</code>），也可以作为选项传递给 <code>#translate</code> 和 <code>#localize</code> 方法。</p><p>如果我们没有传递区域设置，Rails 就会使用 <code>I18n.locale</code>：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
I18n.locale = :de
I18n.t :foo
I18n.l Time.now

</pre>
</div>
<p>显式传递区域设置：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
I18n.t :foo, locale: :de
I18n.l Time.now, locale: :de

</pre>
</div>
<p><code>I18n.locale</code> 的默认值是 <code>I18n.default_locale</code> ，而 <code>I18n.default_locale</code> 的默认值是 <code>:en</code>。可以像下面这样设置默认区域：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
I18n.default_locale = :de

</pre>
</div>
<p><a class="anchor" id="using-safe-html-translations"></a></p><h4 id="using-safe-html-translations">4.4 使用安全 HTML 翻译</h4><p>带有 <code>'_html'</code> 后缀的键和名为 <code>'html'</code> 的键被认为是 HTML 安全的。当我们在视图中使用这些键时，HTML 不会被转义。</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
# config/locales/en.yml
en:
  welcome: &lt;b&gt;welcome!&lt;/b&gt;
  hello_html: &lt;b&gt;hello!&lt;/b&gt;
  title:
    html: &lt;b&gt;title!&lt;/b&gt;

</pre>
</div>
<div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
# app/views/home/index.html.erb
&lt;div&gt;&lt;%= t('welcome') %&gt;&lt;/div&gt;
&lt;div&gt;&lt;%= raw t('welcome') %&gt;&lt;/div&gt;
&lt;div&gt;&lt;%= t('hello_html') %&gt;&lt;/div&gt;
&lt;div&gt;&lt;%= t('title.html') %&gt;&lt;/div&gt;

</pre>
</div>
<p>不过插值是会被转义的。例如，对于：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
en:
  welcome_html: "&lt;b&gt;Welcome %{username}!&lt;/b&gt;"

</pre>
</div>
<p>我们可以安全地传递用户设置的用户名：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;%# This is safe, it is going to be escaped if needed. %&gt;
&lt;%= t('welcome_html', username: @current_user.username) %&gt;

</pre>
</div>
<p>另一方面，安全字符串是逐字插入的。</p><div class="note"><p>只有 <code>translate</code> 视图辅助方法支持 HTML 安全翻译文本的自动转换。</p></div><p><img src="images/i18n/demo_html_safe.png" alt="demo html safe"></p><p><a class="anchor" id="translations-for-active-record-models"></a></p><h4 id="translations-for-active-record-models">4.5 Active Record 模型的翻译</h4><p>我们可以使用 <code>Model.model_name.human</code> 和 <code>Model.human_attribute_name(attribute)</code> 方法，来透明地查找模型名和属性名的翻译。</p><p>例如，当我们添加了下述翻译：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
en:
  activerecord:
    models:
      user: Dude
    attributes:
      user:
        login: "Handle"
      # 会把 User 的属性 "login" 翻译为 "Handle"

</pre>
</div>
<p><code>User.model_name.human</code> 会返回 <code>"Dude"</code>，而 <code>User.human_attribute_name("login")</code> 会返回 <code>"Handle"</code>。</p><p>我们还可以像下面这样为模型名添加复数形式：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
en:
  activerecord:
    models:
      user:
        one: Dude
        other: Dudes

</pre>
</div>
<p>这时 <code>User.model_name.human(count: 2)</code> 会返回 <code>"Dudes"</code>，而 <code>User.model_name.human(count: 1)</code> 或 <code>User.model_name.human</code> 会返回 <code>"Dude"</code>。</p><p>要想访问模型的嵌套属性，我们可以在翻译文件的模型层级中嵌套使用“模型/属性”：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
en:
  activerecord:
    attributes:
      user/gender:
        female: "Female"
        male: "Male"

</pre>
</div>
<p>这时 <code>User.human_attribute_name("gender.female")</code> 会返回 <code>"Female"</code>。</p><div class="note"><p>如果我们使用的类包含了 <code>ActiveModel</code>，而没有继承自 <code>ActiveRecord::Base</code>，我们就应该用 <code>activemodel</code> 替换上述例子中键路径中的 <code>activerecord</code>。</p></div><p><a class="anchor" id="error-message-scopes"></a></p><h5 id="error-message-scopes">4.5.1 错误消息的作用域</h5><p>Active Record 验证的错误消息翻译起来很容易。Active Record 提供了一些用于放置消息翻译的命名空间，以便为不同的模型、属性和验证提供不同的消息和翻译。当然 Active Record 也考虑到了单表继承问题。</p><p>这就为根据应用需求灵活调整信息，提供了非常强大的工具。</p><p>假设 <code>User</code> 模型对 <code>name</code> 属性进行了验证：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class User &lt; ApplicationRecord
  validates :name, presence: true
end

</pre>
</div>
<p>此时，错误信息的键是 <code>:blank</code>。Active Record 会在命名空间中查找这个键：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
activerecord.errors.models.[model_name].attributes.[attribute_name]
activerecord.errors.models.[model_name]
activerecord.errors.messages
errors.attributes.[attribute_name]
errors.messages

</pre>
</div>
<p>因此，在本例中，Active Record 会按顺序查找下列键，并返回第一个结果：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
activerecord.errors.models.user.attributes.name.blank
activerecord.errors.models.user.blank
activerecord.errors.messages.blank
errors.attributes.name.blank
errors.messages.blank

</pre>
</div>
<p>如果模型使用了继承，Active Record 还会在继承链中查找消息。</p><p>例如，对于继承自 <code>User</code> 模型的 <code>Admin</code> 模型：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Admin &lt; User
  validates :name, presence: true
end

</pre>
</div>
<p>Active Record 会按下列顺序查找消息：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
activerecord.errors.models.admin.attributes.name.blank
activerecord.errors.models.admin.blank
activerecord.errors.models.user.attributes.name.blank
activerecord.errors.models.user.blank
activerecord.errors.messages.blank
errors.attributes.name.blank
errors.messages.blank

</pre>
</div>
<p>这样，我们就可以在模型继承链的不同位置，以及属性、模型或默认作用域中，为各种错误消息提供特殊翻译。</p><p><a class="anchor" id="error-message-interpolation"></a></p><h5 id="error-message-interpolation">4.5.2 错误消息的插值</h5><p>翻译后的模型名、属性名，以及值，始终可以通过 <code>model</code>、<code>attribute</code> 和 <code>value</code> 插值。</p><p>因此，举例来说，我们可以用 <code>"Please fill in your %{attribute}"</code> 这样的属性名来代替默认的 <code>"cannot be blank"</code> 错误信息。</p><p>当 <code>count</code> 方法可用时，可根据需要用于复数转换：</p>
<table>
<thead>
<tr>
<th>验证</th>
<th>选项</th>
<th>信息</th>
<th>插值</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>confirmation</code></td>
<td>-</td>
<td><code>:confirmation</code></td>
<td><code>attribute</code></td>
</tr>
<tr>
<td><code>acceptance</code></td>
<td>-</td>
<td><code>:accepted</code></td>
<td>-</td>
</tr>
<tr>
<td><code>presence</code></td>
<td>-</td>
<td><code>:blank</code></td>
<td>-</td>
</tr>
<tr>
<td><code>absence</code></td>
<td>-</td>
<td><code>:present</code></td>
<td>-</td>
</tr>
<tr>
<td><code>length</code></td>
<td>
<code>:within</code>, <code>:in</code>
</td>
<td><code>:too_short</code></td>
<td><code>count</code></td>
</tr>
<tr>
<td><code>length</code></td>
<td>
<code>:within</code>, <code>:in</code>
</td>
<td><code>:too_long</code></td>
<td><code>count</code></td>
</tr>
<tr>
<td><code>length</code></td>
<td><code>:is</code></td>
<td><code>:wrong_length</code></td>
<td><code>count</code></td>
</tr>
<tr>
<td><code>length</code></td>
<td><code>:minimum</code></td>
<td><code>:too_short</code></td>
<td><code>count</code></td>
</tr>
<tr>
<td><code>length</code></td>
<td><code>:maximum</code></td>
<td><code>:too_long</code></td>
<td><code>count</code></td>
</tr>
<tr>
<td><code>uniqueness</code></td>
<td>-</td>
<td><code>:taken</code></td>
<td>-</td>
</tr>
<tr>
<td><code>format</code></td>
<td>-</td>
<td><code>:invalid</code></td>
<td>-</td>
</tr>
<tr>
<td><code>inclusion</code></td>
<td>-</td>
<td><code>:inclusion</code></td>
<td>-</td>
</tr>
<tr>
<td><code>exclusion</code></td>
<td>-</td>
<td><code>:exclusion</code></td>
<td>-</td>
</tr>
<tr>
<td><code>associated</code></td>
<td>-</td>
<td><code>:invalid</code></td>
<td>-</td>
</tr>
<tr>
<td><code>non-optional association</code></td>
<td>-</td>
<td><code>:required</code></td>
<td>-</td>
</tr>
<tr>
<td><code>numericality</code></td>
<td>-</td>
<td><code>:not_a_number</code></td>
<td>-</td>
</tr>
<tr>
<td><code>numericality</code></td>
<td><code>:greater_than</code></td>
<td><code>:greater_than</code></td>
<td><code>count</code></td>
</tr>
<tr>
<td><code>numericality</code></td>
<td><code>:greater_than_or_equal_to</code></td>
<td><code>:greater_than_or_equal_to</code></td>
<td><code>count</code></td>
</tr>
<tr>
<td><code>numericality</code></td>
<td><code>:equal_to</code></td>
<td><code>:equal_to</code></td>
<td><code>count</code></td>
</tr>
<tr>
<td><code>numericality</code></td>
<td><code>:less_than</code></td>
<td><code>:less_than</code></td>
<td><code>count</code></td>
</tr>
<tr>
<td><code>numericality</code></td>
<td><code>:less_than_or_equal_to</code></td>
<td><code>:less_than_or_equal_to</code></td>
<td><code>count</code></td>
</tr>
<tr>
<td><code>numericality</code></td>
<td><code>:other_than</code></td>
<td><code>:other_than</code></td>
<td><code>count</code></td>
</tr>
<tr>
<td><code>numericality</code></td>
<td><code>:only_integer</code></td>
<td><code>:not_an_integer</code></td>
<td>-</td>
</tr>
<tr>
<td><code>numericality</code></td>
<td><code>:odd</code></td>
<td><code>:odd</code></td>
<td>-</td>
</tr>
<tr>
<td><code>numericality</code></td>
<td><code>:even</code></td>
<td><code>:even</code></td>
<td>-</td>
</tr>
</tbody>
</table>
<p><a class="anchor" id="translations-for-the-active-record-error-messages-for-helper"></a></p><h5 id="translations-for-the-active-record-error-messages-for-helper">4.5.3 为 Active Record 的 <code>error_messages_for</code> 辅助方法添加翻译</h5><p>在使用 Active Record 的 <code>error_messages_for</code> 辅助方法时，我们可以为其添加翻译。</p><p>Rails 自带以下翻译：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
en:
  activerecord:
    errors:
      template:
        header:
          one:   "1 error prohibited this %{model} from being saved"
          other: "%{count} errors prohibited this %{model} from being saved"
        body:    "There were problems with the following fields:"

</pre>
</div>
<div class="note"><p>要想使用 <code>error_messages_for</code> 辅助方法，我们需要在 <code>Gemfile</code> 中添加一行 <code>gem 'dynamic_form'</code>，还要安装 <a href="https://github.com/joelmoss/dynamic_form">DynamicForm</a> gem。</p></div><p><a class="anchor" id="translations-for-action-mailer-e-mail-subjects"></a></p><h4 id="translations-for-action-mailer-e-mail-subjects">4.6 Action Mailer 电子邮件主题的翻译</h4><p>如果没有把主题传递给 <code>mail</code> 方法，Action Mailer 会尝试在翻译中查找主题。查找时会使用 <code>&lt;mailer_scope&gt;.&lt;action_name&gt;.subject</code> 形式来构造键。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# user_mailer.rb
class UserMailer &lt; ActionMailer::Base
  def welcome(user)
    #...
  end
end

</pre>
</div>
<div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
en:
  user_mailer:
    welcome:
      subject: "Welcome to Rails Guides!"

</pre>
</div>
<p>要想把参数用于插值，可以在调用邮件程序时使用 <code>default_i18n_subject</code> 方法。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# user_mailer.rb
class UserMailer &lt; ActionMailer::Base
  def welcome(user)
    mail(to: user.email, subject: default_i18n_subject(user: user.name))
  end
end

</pre>
</div>
<div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
en:
  user_mailer:
    welcome:
      subject: "%{user}, welcome to Rails Guides!"

</pre>
</div>
<p><a class="anchor" id="overview-of-other-built-in-methods-that-provide-i18n-support"></a></p><h4 id="overview-of-other-built-in-methods-that-provide-i18n-support">4.7 提供 I18n 支持的其他内置方法概述</h4><p>在 Rails 中，我们会使用固定字符串和其他本地化元素，例如，在一些辅助方法中使用的格式字符串和其他格式信息。本小节提供了简要概述。</p><p><a class="anchor" id="action-view-helper-methods"></a></p><h5 id="action-view-helper-methods">4.7.1 Action View 辅助方法</h5>
<ul>
<li>  <code>distance_of_time_in_words</code> 辅助方法翻译并以复数形式显示结果，同时插入秒、分钟、小时的数值。更多介绍请参阅 <a href="https://github.com/rails/rails/blob/master/actionview/lib/action_view/locale/en.yml#L4">datetime.distance_in_words</a>。</li>
<li>  <code>datetime_select</code> 和 <code>select_month</code> 辅助方法使用翻译后的月份名称来填充生成的 <code>select</code> 标签。更多介绍请参阅 <a href="https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L15">date.month_names</a>。<code>datetime_select</code> 辅助方法还会从 <a href="https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L18">date.order</a> 中查找 <code>order</code> 选项（除非我们显式传递了 <code>order</code> 选项）。如果可能，所有日期选择辅助方法在翻译提示信息时，都会使用 <a href="https://github.com/rails/rails/blob/master/actionview/lib/action_view/locale/en.yml#L39">datetime.prompts</a> 作用域中的翻译。</li>
<li>  <code>number_to_currency</code>、<code>number_with_precision</code>、<code>number_to_percentage</code>、<code>number_with_delimiter</code> 和 <code>number_to_human_size</code> 辅助方法使用 <a href="https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L37">number</a> 作用域中的数字格式设置。</li>
</ul>
<p><a class="anchor" id="active-model-methods"></a></p><h5 id="active-model-methods">4.7.2 Active Model 方法</h5>
<ul>
<li>  <code>model_name.human</code> 和 <code>human_attribute_name</code> 方法会使用 <a href="https://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml#L36">activerecord.models</a> 作用域中可用的模型名和属性名的翻译。像 <a href="#error-message-scopes">错误消息的作用域</a>中介绍的那样，这两个方法也支持继承的类名的翻译（例如，用于 <code>STI</code>）。</li>
<li>  <code>ActiveModel::Errors#generate_message</code> 方法（在 Active Model 验证时使用，也可以手动使用）会使用上面介绍的 <code>model_name.human</code> 和 <code>human_attribute_name</code> 方法。像 <a href="#error-message-scopes">错误消息的作用域</a>中介绍的那样，这个方法也会翻译错误消息，并支持继承的类名的翻译。</li>
<li>  <code>ActiveModel::Errors#full_messages</code> 方法使用分隔符把属性名添加到错误消息的开头，然后在 <a href="https://github.com/rails/rails/blob/master/activemodel/lib/active_model/locale/en.yml#L4">errors.format</a> 中查找（默认格式为 <code>"%{attribute} %{message}"</code>）。</li>
</ul>
<p><a class="anchor" id="active-support-methods"></a></p><h5 id="active-support-methods">4.7.3 Active Support 方法</h5>
<ul>
<li>  <code>Array#to_sentence</code> 方法使用 <a href="https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L33">support.array</a> 作用域中的格式设置。</li>
</ul>
<p><a class="anchor" id="how-to-store-your-custom-translations"></a></p><h3 id="how-to-store-your-custom-translations">5 如何储存自定义翻译</h3><p>Active Support 自带的简单后端，允许我们用纯 Ruby 或 YAML 格式储存翻译。</p><p>通过 Ruby 散列储存翻译的示例如下：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
{
  pt: {
    foo: {
      bar: "baz"
    }
  }
}

</pre>
</div>
<p>对应的 YAML 文件如下：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
pt:
  foo:
    bar: baz

</pre>
</div>
<p>正如我们看到的，在这两种情况下，顶层的键是区域设置。<code>:foo</code> 是命名空间的键，<code>:bar</code> 是翻译 <code>"baz"</code> 的键。</p><p>下面是来自 Active Support 自带的 YAML 格式的翻译文件 <code>en.yml</code> 的“真实”示例：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
en:
  date:
    formats:
      default: "%Y-%m-%d"
      short: "%b %d"
      long: "%B %d, %Y"

</pre>
</div>
<p>因此，下列查找效果相同，都会返回短日期格式 <code>"%b %d"</code>：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
I18n.t 'date.formats.short'
I18n.t 'formats.short', scope: :date
I18n.t :short, scope: 'date.formats'
I18n.t :short, scope: [:date, :formats]

</pre>
</div>
<p>一般来说，我们推荐使用 YAML 作为储存翻译的格式。然而，在有些情况下，我们可能需要把 Ruby lambda 作为储存的区域设置信息的一部分，例如特殊的日期格式。</p><p><a class="anchor" id="customize-your-i18n-setup"></a></p><h3 id="customize-your-i18n-setup">6 自定义 I18n 设置</h3><p><a class="anchor" id="using-different-backends"></a></p><h4 id="using-different-backends">6.1 使用不同的后端</h4><p>由于某些原因，Active Support 自带的简单后端只为 Ruby on Rails 做了“完成任务所需的最少量工作”，这意味着只有对英语以及和英语高度类似的语言，简单后端才能保证正常工作。此外，简单后端只能读取翻译，而不能动态地把翻译储存为任何格式。</p><p>这并不意味着我们会被这些限制所困扰。Ruby I18n gem 让我们能够轻易地把简单后端替换为其他更适合实际需求的后端。例如，我们可以把简单后端替换为 Globalize 的 Static 后端：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
I18n.backend = Globalize::Backend::Static.new

</pre>
</div>
<p>我们还可以使用 Chain 后端，把多个后端链接在一起。当我们想要通过简单后端使用标准翻译，同时把自定义翻译储存在数据库或其他后端中时，链接多个后端的方式非常有用。例如，我们可以使用 Active Record 后端，并在需要时退回到默认的简单后端：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n.backend)

</pre>
</div>
<p><a class="anchor" id="using-different-exception-handlers"></a></p><h4 id="using-different-exception-handlers">6.2 使用不同的异常处理程序</h4><p>I18n API 定义了下列异常，这些异常会在相应的意外情况发生时由后端抛出：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
MissingTranslationData       # 找不到键对应的翻译
InvalidLocale                # I18n.locale 的区域设置无效（例如 nil）
InvalidPluralizationData     # 传递了 count 参数，但翻译数据无法转换为复数形式
MissingInterpolationArgument # 翻译所需的插值参数未传递
ReservedInterpolationKey     # 翻译包含的插值变量名使用了保留关键字（例如，scope 或 default）
UnknownFileType              # 后端不知道应该如何处理添加到 I18n.load_path 中的文件类型

</pre>
</div>
<p>当后端抛出上述异常时，I18n API 会捕获这些异常，把它们传递给 <code>default_exception_handler</code> 方法。这个方法会再次抛出除了 <code>MissingTranslationData</code> 之外的异常。当捕捉到 <code>MissingTranslationData</code> 异常时，这个方法会返回异常的错误消息字符串，其中包含了所缺少的键/作用域。</p><p>这样做的原因是，在开发期间，我们通常希望在缺少翻译时仍然渲染视图。</p><p>不过，在其他上下文中，我们可能想要改变此行为。例如，默认的异常处理程序不允许在自动化测试期间轻易捕获缺少的翻译；要改变这一行为，可以使用不同的异常处理程序。所使用的异常处理程序必需是 I18n 模块中的方法，或具有 <code>#call</code> 方法的类。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
module I18n
  class JustRaiseExceptionHandler &lt; ExceptionHandler
    def call(exception, locale, key, options)
      if exception.is_a?(MissingTranslationData)
        raise exception.to_exception
      else
        super
      end
    end
  end
end

I18n.exception_handler = I18n::JustRaiseExceptionHandler.new

</pre>
</div>
<p>这个例子中使用的异常处理程序只会重新抛出 <code>MissingTranslationData</code> 异常，并把其他异常传递给默认的异常处理程序。</p><p>不过，如果我们使用了 <code>I18n::Backend::Pluralization</code> 异常处理程序，则还会抛出 <code>I18n::MissingTranslationData: translation missing: en.i18n.plural.rule</code> 异常，而这个异常通常应该被忽略，以便退回到默认的英语区域设置的复数转换规则。为了避免这种情况，我们可以对翻译键进行附加检查：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
if exception.is_a?(MissingTranslationData) &amp;&amp; key.to_s != 'i18n.plural.rule'
  raise exception.to_exception
else
  super
end

</pre>
</div>
<p>默认行为不太适用的另一个例子，是 Rails 的 <code>TranslationHelper</code> 提供的 <code>#t</code> 辅助方法（和 <code>#translate</code> 辅助方法）。当上下文中出现了 <code>MissingTranslationData</code> 异常时，这个辅助方法会把错误消息放到 <code>&lt;span class="translation_missing"&gt;</code> 元素中。</p><p>不管是什么异常处理程序，这个辅助方法都能够通过设置 <code>:raise</code> 选项，强制 <code>I18n#translate</code> 方法抛出异常：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
I18n.t :foo, raise: true # 总是重新抛出来自后端的异常

</pre>
</div>
<p><a class="anchor" id="conclusion"></a></p><h3 id="conclusion">7 结论</h3><p>现在，我们已经对 Ruby on Rails 的 I18n 支持有了较为全面的了解，可以开始着手翻译自己的项目了。</p><p>如果想参加讨论或寻找问题的解答，可以注册 <a href="http://groups.google.com/group/rails-i18n">rails-i18n 邮件列表</a>。</p><p><a class="anchor" id="contributing-to-rails-i18n"></a></p><h3 id="contributing-to-rails-i18n">8 为 Rails I18n 作贡献</h3><p>I18n 是在 Ruby on Rails 2.2 中引入的，并且仍在不断发展。该项目继承了 Ruby on Rails 开发的优良传统，各种解决方案首先应用于 gem 和真实应用，然后再把其中最好和最广泛使用的部分纳入 Rails 核心。</p><p>因此，Rails 鼓励每个人在 gem 或其他库中试验新想法和新特性，并将它们贡献给社区。（别忘了在邮件列表上宣布我们的工作！）</p><p>如果在 Ruby on Rails 的<a href="https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale">示例翻译数据</a>库中没找到想要的区域设置（语言），可以<a href="https://github.com/guides/fork-a-project-and-submit-your-modifications">派生仓库</a>，添加翻译数据，然后发送<a href="https://help.github.com/articles/about-pull-requests/">拉取请求</a>。</p><p><a class="anchor" id="resources"></a></p><h3 id="resources">9 资源</h3>
<ul>
<li>  <a href="http://groups.google.com/group/rails-i18n">rails-i18n Google 群组</a>：项目的邮件列表。</li>
<li>  <a href="https://github.com/svenfuchs/rails-i18n">GitHub 中的 rails-i18n 仓库</a>：rails-i18n 项目的代码仓库和问题跟踪器。最重要的是，我们可以在这里找到很多 Rails 的<a href="https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale">示例翻译</a>，在大多数情况下，它们都适用于我们的应用。</li>
<li>  <a href="https://github.com/svenfuchs/i18n">GitHub 中的 i18n 仓库</a>：i18n gem 的代码仓库和问题追踪系统。</li>
</ul>
<p><a class="anchor" id="authors"></a></p><h3 id="authors">10 作者</h3>
<ul>
<li>  <a href="http://svenfuchs.com/">Sven Fuchs</a>（最初的作者）</li>
<li>  <a href="http://www.karmi.cz/">Karel Minařík</a>
</li>
</ul>


        <h3>反馈</h3>
        <p>
          我们鼓励您帮助提高本指南的质量。
        </p>
        <p>
          如果看到如何错字或错误，请反馈给我们。
          您可以阅读我们的<a href="http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation">文档贡献</a>指南。
        </p>
        <p>
          您还可能会发现内容不完整或不是最新版本。
          请添加缺失文档到 master 分支。请先确认 <a href="http://edgeguides.rubyonrails.org">Edge Guides</a> 是否已经修复。
          关于用语约定，请查看<a href="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南指导</a>。
        </p>
        <p>
          无论什么原因，如果你发现了问题但无法修补它，请<a href="https://github.com/rails/rails/issues">创建 issue</a>。
        </p>
        <p>
          最后，欢迎到 <a href="http://groups.google.com/group/rubyonrails-docs">rubyonrails-docs 邮件列表</a>参与任何有关 Ruby on Rails 文档的讨论。
        </p>
        <h4>中文翻译反馈</h4>
        <p>贡献：<a href="https://github.com/ruby-china/guides">https://github.com/ruby-china/guides</a>。</p>
      </div>
    </div>
  </div>

  <hr class="hide" />
  <div id="footer">
    <div class="wrapper">
      <p>本著作采用 <a href="https://creativecommons.org/licenses/by-sa/4.0/">创作共用 署名-相同方式共享 4.0 国际</a> 授权</p>
<p>“Rails”，“Ruby on Rails”，以及 Rails Logo 为 David Heinemeier Hansson 的商标。版权所有</p>

    </div>
  </div>

  <script type="text/javascript" src="javascripts/jquery.min.js"></script>
  <script type="text/javascript" src="javascripts/responsive-tables.js"></script>
  <script type="text/javascript" src="javascripts/guides.js"></script>
  <script type="text/javascript" src="javascripts/syntaxhighlighter.js"></script>
  <script type="text/javascript">
    syntaxhighlighterConfig = {
      autoLinks: false,
    };
    $(guidesIndex.bind);
  </script>
</body>
</html>
